{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:19.813472200Z",
     "start_time": "2024-07-19T07:08:19.713998Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.9.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.5.0\n",
      "torch 2.3.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.163435800Z",
     "start_time": "2024-07-19T07:08:19.732515200Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. topic:: References\n",
      "\n",
      "    - Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "      Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.170910700Z",
     "start_time": "2024-07-19T07:08:20.165434500Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.631878500Z",
     "start_time": "2024-07-19T07:08:20.173909400Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.646385200Z",
     "start_time": "2024-07-19T07:08:20.630877Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "StandardScaler()",
      "text/html": "<style>#sk-container-id-1 {\n  /* Definition of color scheme common for light and dark mode */\n  --sklearn-color-text: black;\n  --sklearn-color-line: gray;\n  /* Definition of color scheme for unfitted estimators */\n  --sklearn-color-unfitted-level-0: #fff5e6;\n  --sklearn-color-unfitted-level-1: #f6e4d2;\n  --sklearn-color-unfitted-level-2: #ffe0b3;\n  --sklearn-color-unfitted-level-3: chocolate;\n  /* Definition of color scheme for fitted estimators */\n  --sklearn-color-fitted-level-0: #f0f8ff;\n  --sklearn-color-fitted-level-1: #d4ebff;\n  --sklearn-color-fitted-level-2: #b3dbfd;\n  --sklearn-color-fitted-level-3: cornflowerblue;\n\n  /* Specific color for light theme */\n  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n  --sklearn-color-icon: #696969;\n\n  @media (prefers-color-scheme: dark) {\n    /* Redefinition of color scheme for dark theme */\n    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n    --sklearn-color-icon: #878787;\n  }\n}\n\n#sk-container-id-1 {\n  color: var(--sklearn-color-text);\n}\n\n#sk-container-id-1 pre {\n  padding: 0;\n}\n\n#sk-container-id-1 input.sk-hidden--visually {\n  border: 0;\n  clip: rect(1px 1px 1px 1px);\n  clip: rect(1px, 1px, 1px, 1px);\n  height: 1px;\n  margin: -1px;\n  overflow: hidden;\n  padding: 0;\n  position: absolute;\n  width: 1px;\n}\n\n#sk-container-id-1 div.sk-dashed-wrapped {\n  border: 1px dashed var(--sklearn-color-line);\n  margin: 0 0.4em 0.5em 0.4em;\n  box-sizing: border-box;\n  padding-bottom: 0.4em;\n  background-color: var(--sklearn-color-background);\n}\n\n#sk-container-id-1 div.sk-container {\n  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n     but bootstrap.min.css set `[hidden] { display: none !important; }`\n     so we also need the `!important` here to be able to override the\n     default hidden behavior on the sphinx rendered scikit-learn.org.\n     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n  display: inline-block !important;\n  position: relative;\n}\n\n#sk-container-id-1 div.sk-text-repr-fallback {\n  display: none;\n}\n\ndiv.sk-parallel-item,\ndiv.sk-serial,\ndiv.sk-item {\n  /* draw centered vertical line to link estimators */\n  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n  background-size: 2px 100%;\n  background-repeat: no-repeat;\n  background-position: center center;\n}\n\n/* Parallel-specific style estimator block */\n\n#sk-container-id-1 div.sk-parallel-item::after {\n  content: \"\";\n  width: 100%;\n  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n  flex-grow: 1;\n}\n\n#sk-container-id-1 div.sk-parallel {\n  display: flex;\n  align-items: stretch;\n  justify-content: center;\n  background-color: var(--sklearn-color-background);\n  position: relative;\n}\n\n#sk-container-id-1 div.sk-parallel-item {\n  display: flex;\n  flex-direction: column;\n}\n\n#sk-container-id-1 div.sk-parallel-item:first-child::after {\n  align-self: flex-end;\n  width: 50%;\n}\n\n#sk-container-id-1 div.sk-parallel-item:last-child::after {\n  align-self: flex-start;\n  width: 50%;\n}\n\n#sk-container-id-1 div.sk-parallel-item:only-child::after {\n  width: 0;\n}\n\n/* Serial-specific style estimator block */\n\n#sk-container-id-1 div.sk-serial {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  background-color: var(--sklearn-color-background);\n  padding-right: 1em;\n  padding-left: 1em;\n}\n\n\n/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\nclickable and can be expanded/collapsed.\n- Pipeline and ColumnTransformer use this feature and define the default style\n- Estimators will overwrite some part of the style using the `sk-estimator` class\n*/\n\n/* Pipeline and ColumnTransformer style (default) */\n\n#sk-container-id-1 div.sk-toggleable {\n  /* Default theme specific background. It is overwritten whether we have a\n  specific estimator or a Pipeline/ColumnTransformer */\n  background-color: var(--sklearn-color-background);\n}\n\n/* Toggleable label */\n#sk-container-id-1 label.sk-toggleable__label {\n  cursor: pointer;\n  display: block;\n  width: 100%;\n  margin-bottom: 0;\n  padding: 0.5em;\n  box-sizing: border-box;\n  text-align: center;\n}\n\n#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n  /* Arrow on the left of the label */\n  content: \"▸\";\n  float: left;\n  margin-right: 0.25em;\n  color: var(--sklearn-color-icon);\n}\n\n#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n  color: var(--sklearn-color-text);\n}\n\n/* Toggleable content - dropdown */\n\n#sk-container-id-1 div.sk-toggleable__content {\n  max-height: 0;\n  max-width: 0;\n  overflow: hidden;\n  text-align: left;\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content.fitted {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content pre {\n  margin: 0.2em;\n  border-radius: 0.25em;\n  color: var(--sklearn-color-text);\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n  /* unfitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n  /* Expand drop-down */\n  max-height: 200px;\n  max-width: 100%;\n  overflow: auto;\n}\n\n#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n  content: \"▾\";\n}\n\n/* Pipeline/ColumnTransformer-specific style */\n\n#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Estimator-specific style */\n\n/* Colorize estimator box */\n#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n#sk-container-id-1 div.sk-label label {\n  /* The background is the default theme color */\n  color: var(--sklearn-color-text-on-default-background);\n}\n\n/* On hover, darken the color of the background */\n#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n/* Label box, darken color on hover, fitted */\n#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n  color: var(--sklearn-color-text);\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Estimator label */\n\n#sk-container-id-1 div.sk-label label {\n  font-family: monospace;\n  font-weight: bold;\n  display: inline-block;\n  line-height: 1.2em;\n}\n\n#sk-container-id-1 div.sk-label-container {\n  text-align: center;\n}\n\n/* Estimator-specific */\n#sk-container-id-1 div.sk-estimator {\n  font-family: monospace;\n  border: 1px dotted var(--sklearn-color-border-box);\n  border-radius: 0.25em;\n  box-sizing: border-box;\n  margin-bottom: 0.5em;\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-0);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-0);\n}\n\n/* on hover */\n#sk-container-id-1 div.sk-estimator:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-2);\n}\n\n#sk-container-id-1 div.sk-estimator.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-2);\n}\n\n/* Specification for estimator info (e.g. \"i\" and \"?\") */\n\n/* Common style for \"i\" and \"?\" */\n\n.sk-estimator-doc-link,\na:link.sk-estimator-doc-link,\na:visited.sk-estimator-doc-link {\n  float: right;\n  font-size: smaller;\n  line-height: 1em;\n  font-family: monospace;\n  background-color: var(--sklearn-color-background);\n  border-radius: 1em;\n  height: 1em;\n  width: 1em;\n  text-decoration: none !important;\n  margin-left: 1ex;\n  /* unfitted */\n  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n  color: var(--sklearn-color-unfitted-level-1);\n}\n\n.sk-estimator-doc-link.fitted,\na:link.sk-estimator-doc-link.fitted,\na:visited.sk-estimator-doc-link.fitted {\n  /* fitted */\n  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n  color: var(--sklearn-color-fitted-level-1);\n}\n\n/* On hover */\ndiv.sk-estimator:hover .sk-estimator-doc-link:hover,\n.sk-estimator-doc-link:hover,\ndiv.sk-label-container:hover .sk-estimator-doc-link:hover,\n.sk-estimator-doc-link:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\ndiv.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n.sk-estimator-doc-link.fitted:hover,\ndiv.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n.sk-estimator-doc-link.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\n/* Span, style for the box shown on hovering the info icon */\n.sk-estimator-doc-link span {\n  display: none;\n  z-index: 9999;\n  position: relative;\n  font-weight: normal;\n  right: .2ex;\n  padding: .5ex;\n  margin: .5ex;\n  width: min-content;\n  min-width: 20ex;\n  max-width: 50ex;\n  color: var(--sklearn-color-text);\n  box-shadow: 2pt 2pt 4pt #999;\n  /* unfitted */\n  background: var(--sklearn-color-unfitted-level-0);\n  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n}\n\n.sk-estimator-doc-link.fitted span {\n  /* fitted */\n  background: var(--sklearn-color-fitted-level-0);\n  border: var(--sklearn-color-fitted-level-3);\n}\n\n.sk-estimator-doc-link:hover span {\n  display: block;\n}\n\n/* \"?\"-specific style due to the `<a>` HTML tag */\n\n#sk-container-id-1 a.estimator_doc_link {\n  float: right;\n  font-size: 1rem;\n  line-height: 1em;\n  font-family: monospace;\n  background-color: var(--sklearn-color-background);\n  border-radius: 1rem;\n  height: 1rem;\n  width: 1rem;\n  text-decoration: none;\n  /* unfitted */\n  color: var(--sklearn-color-unfitted-level-1);\n  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n}\n\n#sk-container-id-1 a.estimator_doc_link.fitted {\n  /* fitted */\n  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n  color: var(--sklearn-color-fitted-level-1);\n}\n\n/* On hover */\n#sk-container-id-1 a.estimator_doc_link:hover {\n  /* unfitted */\n  background-color: var(--sklearn-color-unfitted-level-3);\n  color: var(--sklearn-color-background);\n  text-decoration: none;\n}\n\n#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n  /* fitted */\n  background-color: var(--sklearn-color-fitted-level-3);\n}\n</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;StandardScaler<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.5/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.690750300Z",
     "start_time": "2024-07-19T07:08:20.650382500Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.710485600Z",
     "start_time": "2024-07-19T07:08:20.662086500Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n tensor([1.5140]))"
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.711485400Z",
     "start_time": "2024-07-19T07:08:20.673710700Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 16\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.712484300Z",
     "start_time": "2024-07-19T07:08:20.691263Z"
    }
   },
   "outputs": [],
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.712484300Z",
     "start_time": "2024-07-19T07:08:20.696254700Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.798794300Z",
     "start_time": "2024-07-19T07:08:20.713483800Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.798794300Z",
     "start_time": "2024-07-19T07:08:20.731609100Z"
    }
   },
   "outputs": [],
   "source": [
    "# 自定义MSEloss\n",
    "\n",
    "def mse_loss(array1, array2):\n",
    "    return ((array1 - array2) ** 2).mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "outputs": [
    {
     "data": {
      "text/plain": "tensor(0.3750)"
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "# 假设 y_true 是真实值的张量，y_pred 是模型预测值的张量\n",
    "y_true = torch.tensor([3, -0.5, 2, 7])\n",
    "y_pred = torch.tensor([2.5, 0.0, 2.0, 8])\n",
    "\n",
    "# 计算 MSE 损失\n",
    "loss = F.mse_loss(y_pred, y_true)\n",
    "loss"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.799793500Z",
     "start_time": "2024-07-19T07:08:20.744600900Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "outputs": [
    {
     "data": {
      "text/plain": "tensor(0.3750)"
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mse_loss(y_pred, y_true)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-19T07:08:20.799793500Z",
     "start_time": "2024-07-19T07:08:20.761424Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:10:44.668653400Z",
     "start_time": "2024-07-19T07:08:51.704003100Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "  0%|          | 0/72600 [00:00<?, ?it/s]",
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "a6d341e8dc564df6acf854ea60514caa"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 60 / global_step 43560\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用自定义的MSE\n",
    "loss_fct = mse_loss\n",
    "# loss_fct = F.mse_loss\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:18:35.530524600Z",
     "start_time": "2024-07-19T07:18:35.324055400Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 640x480 with 1 Axes>",
      "image/png": "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XdcB0vog9a3V8tOp8fndg3DzJ5td2Y7mXrQpY2BBjRLrXA1vxcEczFLYcE9rc/+ViWhrPIwgA98OmpMx3WMQbNDAaJL275k+KP6yZ1MAUn0Jg4qmYdCq8eU9g7DxRB52pl/EzjMF2JtZCJNVKppt6AqzIf5afH3vENzyyRaUmm3NvmhTxncSarThnSNdUxaJyLMMWjWu79sW327NgFYQcf+Vib5uEtWDRq3CyKRojEySCmOtdgcOnTPiTH5pg9f+AKSTuJVPjoDdIV7WsgXe1KgB1Q8++ACJiYkwGAwYMmQItntq/3giInK578qOSIzwx8QEB6I9kK0g75MXZZzSt12jV+41aNUtJqgAGhFYfP/995gzZw5efPFF7N69G3369MH48eORm5vbFO0jImq1OkQGIOWJKzGqrft9Q4iaowaHQG+//TYeeOAB3HPPPQCAjz/+GEuXLsXnn3+O5557rtrxZrMZZnPFRjhGozSX12q1wmqteQe3hpJvy5O3SZ7D/mm+2DfNG/un+WptfVPfxymI7vadrYHFYoG/vz8WL16MqVOnui6/++67UVhYiF9//bXa38ydOxfz5s2rdvnChQvh788CIiIiopagrKwMt99+O4qKihAcXPN6HA3KWOTl5cFutyMmpuqWrDExMTh69Kjbv3n++ecxZ84c1+9GoxHx8fEYN25crQ1rKKvVipSUFIwdOxZabfOcm96asX+aL/ZN88b+ab5aW9/IIw51afJqEL1eD72+etGRVqttko5oqtslz2D/NF/sm+aN/dN8tZa+qe9jbFDxZmRkJNRqNc6fr7rRyvnz5xEb23KXPiYiIiLPaFBgodPpMGDAAKxevdp1mcPhwOrVqzF06FCPN46IiIhalgYPhcyZMwd33303Bg4ciMGDB+Odd95BaWmpa5YIERERtV4NDixuvfVWXLhwAS+88AJycnLQt29frFixolpBJxEREbU+jSrenD17NmbPnu3pthAREVEL13r2SCYiIqImx8CCiIiIPIaBBREREXkMAwsiIiLyGAYWRERE5DFe3+Bd3vOsvmuO15fVakVZWRmMRmOrWFq1pWH/NF/sm+aN/dN8tba+kT+369q71OuBRXFxMQAgPj7e23dNREREl6m4uBghISE1Xt+gbdM9weFw4Ny5cwgKCoIgCB67XXnX1MzMTI/umkqewf5pvtg3zRv7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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:18:38.306948900Z",
     "start_time": "2024-07-19T07:18:38.230520200Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3291\n"
     ]
    }
   ],
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
